本人水平有限,本文主要是记录自己的学习过程,仅供参考。
最近在做的一款游戏是音乐类游戏,引擎版本用的 Cocos2d-X v3.5 。游戏中五线谱按照音乐的节奏在屏幕上方滚动,玩家通过音乐旋律以及五线谱的指示有节奏的敲击按键来得分。之前的方案是用 Tiled Map 手动拼出乐谱层,在对象层上设置每个音符对象的若干数据,这样整首乐谱的信息就都可以通过 Map 文件得到了。但是显然这种方法费时费力,自然想到寻找其他方案来代替。
乐理知识
在这之前需要了解一些乐理知识以便更好的学习 MusicXML ,只需要非常基础的乐理知识就够了。不赘述。
什么是 MusicXML
MusicXML(Music Extensible Markup Language 音乐扩展标记语言)是一个开放的基于XML 的音乐符号文件格式,由Reccordare 公司开发,该技术源于几个现有的基于学术上的关键技术和想法,比如Walter Hewlett 的MuseData 和David Huron 的Humdrum,他被设计用来做为乐谱信息的交换格式,特别是在不同的乐谱显示软件的之间进行交换。MusicXML 将整体乐曲元素和属性信息表示为一份XML 文档,他克服了另外两种格式—-NIFF 格式(基于图片)和SMDL 格式(过于庞大)的兼容性差,结构复杂等许多缺点,目前他已经被很多应用软件的支持。
MusicXML 的 “Hello World”
这部分翻译自官方文档(渣翻)
Brian Kernighan 和 Dennis Ritchie 推行了当学习一门新的编程语言时先写一个程序打印”hello, world”这一做法。它是测试如何构建一个程序并显示其结果的最小的程序。
在 MusicXML 中,一首有”hello,World”歌词的歌实际上要比我们期望的简单的 MusicXML 文件更复杂。我们要让事情简单一些:一个只包含一个全音符中央C的 4/4 拍小节:
MusicXML 中是这个样子:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<part-list>
<score-part id="P1">
<part-name>Music</part-name>
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>1</divisions>
<key>
<fifths>0</fifths>
</key>
<time>
<beats>4</beats>
<beat-type>4</beat-type>
</time>
<clef>
<sign>G</sign>
<line>2</line>
</clef>
</attributes>
<note>
<pitch>
<step>C</step>
<octave>4</octave>
</pitch>
<duration>4</duration>
<type>whole</type>
</note>
</measure>
</part>
</score-partwise>
我们依次来看各个部分:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
这是所有 XML 文档都有的 XML 声明。我们指定了 “UTF-8” 编码格式。这是有 ASCII 子集的 Unicode 版本。设置 standalone 的值为 “no” 意思是我们在另一个文件外部定义文档。
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
这里是说我们在哪里使用 MusicXML。我们为 DTD 使用了一个包括一个 Internet 位置的 Public 声明。声明中的 URL 只是为了引用。大多数使用 MusicXML 文件的应用都会想要在用户的机器上安装一个 MusicXML DTDs 的本地拷贝。在你的 XML解析器中使用实体解析器来验证本地拷贝,而不是通过网络缓慢地读取 DTDs。如果你的应用想要验证 MusicXML XSD 而不是 DTD,你可以在你的 XML 解析器中用实体解析器来完成。当写入 MusicXML 时,对于所有基于 DTD 或 XSD 的应用来说,写入 DOCTYPE 使得验证 MusicXML 更简单。
<score-partwise version="3.0">
这是根文档类型。元素 score-partwise 是由几部分构成,而每部分由小节构成。这里同样有一个由小节构成的 score-timewise 可选元素,而每小节又是由几部分构成。这个 version 属性让程序更容易分辨出当前使用的 MusicXML 版本。如果你写的是 MusicXML 1.0 文件,那你就不用管它了。
<part-list>
<score-part id="P1">
<part-name>Part 1</part-name>
</score-part>
</part-list>
MusicXML 开始于一个列出了乐谱不同音乐部分的 header(译注:就是上面这段代码) 。上面的例子是尽可能最小的 part-list:它包含了一个拥有 id 属性和 part-name 元素的 score-part 元素。
<part id="P1">
现在我们开始文档的第一部分(这个例子中的唯一一部分)。这里的 id 属性必须引用上面那个 header 中 score-part 元素的 id 属性。
<measure number="1">
我们开始第一部分第一小节。
<attributes>
attributes 元素包含了解析这部分音符和音乐数据所需的关键信息。
<divisions>1</divisions>
MusicXML 中每个音符都有 duration 元素。这个 division 元素为 duration 元素提供了小节的单元,小节的单元以每个四分音符的 divisions 为标准。因为在这里整个文件只有一个全音符,所以我们不用划分四分音符,于是我们把 divisions 的值设为了 1。 音乐上的时值通常用分数表示,比如四分音符、八分音符之类的。MusicXML 的时值也是分数。由于分母很少需要改变,所以在表示 divisions 元素时我们把它分离开不管它。这样就只有分子与每个单独的音符相关。这与 MIDI 中音符时值的表示相似。
<key>
<fifths>0</fifths>
</key>
key 元素用于表示调号。在这我们是C大调调号没有升降号,所以 fifths 元素是 0 。如果我们是 D大调调号有两个升号, fifths 就应该是 2 。如果我们是 F大调调号有一个降号,那么 fifths 应该是 -1 。 “fifths” 这个名字来自五度圈(译注:这个东西有助于理解Circle of fifths)。它让我们可以用一个元素表示标准调号,而不是用不同的元素表示升降号。
<time>
<beats>4</beats>
<beat-type>4</beat-type>
</time>
time 元素表示拍号。它的两个构成元素 beats 和 beat-type 分别是拍号的分子和分母。
<clef>
<sign>G</sign>
<line>2</line>
</clef>
MusicXML 允许许多不同的谱号,其中包括许多今天不再用的。这里,标准的高音谱号用一个在五线谱第二条线上的G谱号表示(也就是说五线谱从下往上第二条线是G)。
</attributes>
<note>
我们已经完成了 attributes 元素,准备开始第一个音符。
<pitch>
<step>C</step>
<octave>4</octave>
</pitch>
pitch 元素必须有一个 step 和一个 octave 元素。如果涉及到了升降号,还可以有一个可选的 alter 元素。这个元素表示声音,所以如果使用了 alter 元素那么 alter 元素必须总是被包含,即使升降号已经在调号中了。在此例中,我们没有升降号。step 是 C。octaved 的 4 表示 octave 开始于中央C。因此这个音符是中央C。
<duration>4</duration>
我们的 divisions 值为 1,所以 duaration 的 4 是 4 个四分音符的长度。
<type>whole</type>
type 元素告诉我们这是个全音符。在此例中你可能可以通过 duration 推导出来,但是如果应用是乐谱和执行数据分开表示的直接给出来更容易操作。不管怎样,在实际中乐谱和执行的数据并不总是匹配在一起的。比如,你想要接近爵士乐那种摇摆飘忽的感觉你就不会用相等长度的八分音符,而是让音符时值在八分音符的基础上浮动,而音符本身还是八分音符。Bach 的音乐就包含时值并不标准的音符。duration 元素应该反映目的时值,不是有偏差的时值,尤其是在特定的音乐中。note 元素有 attack 和 release 属性,可以从乐谱上表示的时值出发,改变音符的开始和结束时间。
</note>
我们完成了 note 元素。
</measure>
我们完成了 measure 元素。
</part>
我们完成了 part 元素。
</score-partwise>
这样整个乐谱就完成了。